import { track, trigger, pauseTrack, resumeTrack } from "./effect.js";
import { isObejct, hasNoChange, isFunction, ISPROXY } from "./utils.js";
import { trackTypes, triggerTypes } from "./effectType.js";
import reactive from "./reactive.js";
const RAW = Symbol("raw");
export const ISREACTIVE = Symbol("isReactive");
const arrayProxyMethods = {};
// 读
// reason-1
// 由于数组的代理对象查找引用数据类型时，返回的是的引用数据类型的代理对象，而不是原始对象，因此要对数组代理的查找方法进行改写
// 应用规则：当使用indexOf、includes、lastIndexOf、findIndex、find、findLastIndex、findLast、every、some等查找方法时，如果查找的值是引用数据类型
// 先用代理对象查找一边，如果找不到，则用原始对象再查找一遍，如果找到了，则返回原始对象在原始数组中的索引，如果找不到，则返回-1
const GETKEYS = [
  "indexOf",
  "includes",
  "lastIndexOf",
  "findIndex",
  "find",
  "findLastIndex",
  "findLast",
  "every",
  "some",
];
GETKEYS.forEach((key) => {
  arrayProxyMethods[key] = function (...args) {
    let res = Array.prototype[key].apply(this, args);
    if (res < 0 || !res) {
      console.log("未找到");
      res = Array.prototype[key].apply(this[RAW], args);
    }
    if (isObejct(res)) {
      // 由于做了对象和代理对象的映射关系,所以，这里返回的代理对象是一致的，无需重新创建
      return reactive(res);
    }
    return res;
  };
});

// 写
// reason-2
// 如果新增属性的对象是数组，数组的改动逻辑，在底层来看，会隐式改变数组的length属性，这是代理对象监控不到的地方
// 因此，需要针对这种情况，我们需要手动做length属性的派发更新
// 1.新增属性的对象是数组 2.新增的属性触发了length属性的隐式改变，例如： Object.defineProperty()新增属性，会触发length属性的隐式改变
// 3.没有直接改变length属性

// reason-3
// 直接改变length属性的值，如果改动的length小于于当前数组长度，那么length后面的元素，会被自动被删除，但是代理对象监控不到
// 因此，需要针对这种情况，我们需要手动对‘被自动删除的元素’做派发更新
// 1.改变length属性的对象是数组 2.改变后的length小于于当前数组长度

// reason-4
// 数组有很多方法，例如：push、pop、shift、unshift、splice、sort、reverse等，这些方法会改变数组，这些方法在调用时，底层会额外取读取数组的一些信息，例如：length、索引等
// 但是，这些信息在修改属性时，并不想被监控到并触发“额外的依赖收集”，因此，要在调用这些方法修改数组时，替换原有的方法，手动屏蔽“额外的依赖收集”
const SETKEYS = [
  "push",
  "pop",
  "shift",
  "unshift",
  "splice",
  "sort",
  "reverse",
];
SETKEYS.forEach((key) => {
  arrayProxyMethods[key] = function (...args) {
    // 手动屏蔽“额外的依赖收集”
    pauseTrack();
    // 调用数组方法修改数组
    const res = Array.prototype[key].apply(this, args);
    // 恢复“额外的依赖收集”
    resumeTrack();
    return res;
  };
});

// 由于读取对象属性信息和修改对象属性信息有多种，所以要对其做类型区分，例如：读取属性信息有：get、has、ownKeys，修改属性信息有：set、add、delete等操作
function get(target, key, reciver) {
  // 添加ISPROXY(符号Symbol)属性，拦截代理对象的属性读取操作
  if (key === ISPROXY) return true;
  // 添加Symbol("raw")属性，拦截代理对象的属性读取操作，返回原始对象
  if (key === RAW) return target;
  // 添加ISREACTIVE(符号Symbol)属性，拦截代理对象的属性读取操作，返回true
  if (key === ISREACTIVE) return true;
  const shallow = target.__v_shallow__;
  // 依赖收集
  // 读取属性本质上就是调用对象的[[get]]内部方法，该内部方法接收两个参数，第一个参数是this指向，第二个参数是读取的属性名
  // 针对属性是getter函数的情况，内部使用this读取的属性，无法追踪依赖，例如：{ get c () { return this.a + this.b } }
  // 这是因为target[key]这种直接读取属性值的形式，默认指定了this指向target，而不是reciver代理本身，自然无法触发get拦截并作追踪
  // return target[key];
  // 因此可以通过Reflect.get来读取属性，该方法可以指定this指向，将其指向给reciver代理对象，就可以做拦截并追踪依赖了
  const res = Reflect.get(target, key, reciver);
  if (res !== undefined) {
    track(target, trackTypes.GET, key);
  }
  // 此if逻辑具体看reason-1
  if (
    isFunction(res) &&
    arrayProxyMethods.hasOwnProperty(key) &&
    Array.isArray(target)
  ) {
    return arrayProxyMethods[key]; // 方法替换成改写方法
  }
  // 但是，如果读取的属性还是引用类型的值，那么该属性值内部的数据属性信息，也需要做依赖收集，也就是深度监听
  // 当shallow为true时，不做深度监听，只做浅层代理
  if (isObejct(res) && !shallow) {
    console.log(target,'深度监听')
    // 深度监听该属性值
    return reactive(res);
  }
  return res;
}

function has(target, key) {
  const res = Reflect.has(target, key);
  if (res) {
    // 如果该属性都没有，还怎么收集依赖呢，所以，只对存在的属性做依赖收集
    track(target, trackTypes.HAS, key);
  }
  return res;
}

function ownKeys(target, key) {
  // console.log("ownKeys", key);
  track(target, trackTypes.ITERATE);
  return Reflect.ownKeys(target);
}

function set(target, key, value) {
  // 区分新增与修改
  const type = target.hasOwnProperty(key) ? "SET" : "ADD";
  const oldValue = target[key];
  const oldLen = Array.isArray(target) ? target.length : undefined;
  const res = Reflect.set(target, key, value);
  const newLen = Array.isArray(target) ? target.length : undefined;
  // 默认情况下，只有当新值和旧值不相等时，才会触发更新
  if (!hasNoChange(oldValue, value) && res) {
    // 派发更新
    trigger(target, triggerTypes[type], key, value, oldValue);
  }

  // 针对没有监控到的属性变化，手动触发更新
  if (Array.isArray(target) && oldLen !== newLen) {
    if (key !== "length") {
      // reason-2
      trigger(target, triggerTypes.SET, "length", newLen, oldLen);
    } else {
      // reason-3
      for (let i = newLen; i < oldLen; i++) {
        trigger(target, triggerTypes.DELETE, i.toString());
      }
    }
  }
  return res;
}

function deleteProperty(target, key) {
  // 没有的属性，还怎么删除呢，所以，只对存在的属性做删除操作追踪
  if (!target.hasOwnProperty(key)) return;
  const res = Reflect.deleteProperty(target, key);
  if (res) {
    // 删除属性成功追踪
    trigger(target, triggerTypes.DELETE, key);
  }
  return res;
}

export default function createReactiveHandle(shallow) {
  return {
    // 只能针对对象属性的读取操作进行拦截
    get,
    // 判断属性是否存在也是读取数据属性信息的过程，因此也需要做依赖收集，例如 'a' in obj
    has,
    // 直接读取对象的key键名合集，本质上也是读取数据属性信息的过程，因此也需要做依赖收集，例如：Object.keys(obj)
    ownKeys,
    // 新增或修改属性
    set,
    // 删除属性
    deleteProperty,
  };
}
